using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Keys = Microsoft.Xna.Framework.Input.Keys;

namespace XNASubdivision
{
    struct StatsData
    {
        public uint nRenderedFrames;
        public double lastStatsUpdateTimeSec;
        public string currentFrameRate;
        public double minFPS;
        public double maxWait;
        public string usedVgaMemory;
        public double lastTime;
    }

    public class GameMain : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager m_graphics;
        ContentManager m_contentManager;
        CatmullClarkSubdivider m_subdivider = new CatmullClarkSubdivider();
        CatmullClarkSubdivider_GS_Imitation m_subdividerGSImitation = new CatmullClarkSubdivider_GS_Imitation();

        KeyboardState m_oldKeyboardState;

        Matrix m_worldMatrix;
        Matrix m_viewMatrix;
        Matrix m_projectionMatrix;

        BasicEffect m_basicEffect;
        VertexDeclaration m_basicEffectVertexDecl;

        bool m_isWireFrame;
        List<QuadMesh> m_quadMeshes = new List<QuadMesh>(9);
        DXTriangleMesh m_triangleMesh;
        
        bool m_isDrawTriad = true;
        VertexDeclaration m_vertexDeclTriad;
        VertexBuffer m_vertexBufferTriad;
        
        Effect m_effect;
        Texture2D m_texture2D;
        Color m_diffuseColor = new Color(95, 95, 95);

        FirstPersonShooterCamera m_camera;

        private SpriteFont m_font;
        SpriteBatch m_foregroundBatch;

        bool m_isShowFps = true;
        StatsData m_statsData;
          
        public GameMain()
        {
            m_graphics = new GraphicsDeviceManager(this);
            m_contentManager = new ContentManager(Services);

            m_oldKeyboardState = Keyboard.GetState();

            IsMouseVisible = true;

            m_statsData.currentFrameRate = "NAN";

            m_graphics.SynchronizeWithVerticalRetrace = false;

            //this.Window.

            //this.Is
        }

        protected override void Initialize()
        {
            InitializeCameraTransforms();
            InitializeEffects();
            InitializeTriad();

            m_camera = new FirstPersonShooterCamera(this);
            m_camera.IsLeftHanded = true;
           
            //m_camera.Position = new Vector3(-2, -2, -2);
            //m_camera.Position = new Vector3(-9, 25, 43);
            m_camera.Position = new Vector3(-55.0f, 13.0f, -27.0f);
            this.Components.Add(m_camera);

            m_graphics.GraphicsDevice.RenderState.PointSize = 10;

            base.Initialize();
        }

        private void InitializeCameraTransforms()
        {
            float tilt = MathHelper.ToRadians(22.5f);  // 22.5 degree angle
            // Use the world matrix to tilt the cube along x and y axis
            //worldMatrix = Matrix.CreateRotationX(tilt) * Matrix.CreateRotationY(tilt);
            m_worldMatrix = Matrix.Identity;

            //m_viewMatrix = Matrix.CreateLookAt(new Vector3(-2, 2, 2), Vector3.Zero, Vector3.Up);
            //m_viewMatrix = Matrix.CreateLookAt(new Vector3(-20, 20, 20), Vector3.Zero, Vector3.Up);
            
            //m_viewMatrix = FirstPersonShooterCamera.CreateLookAtLH(new Vector3(-20, 20, 20), Vector3.Zero, Vector3.Up);
            //m_viewMatrix = FirstPersonShooterCamera.CreateLookAtLH(new Vector3(-55.0f, 13.0f, -27.0f), Vector3.Zero, Vector3.Up);

            //m_projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),  
            //    (float)m_graphics.GraphicsDevice.Viewport.Width / (float)m_graphics.GraphicsDevice.Viewport.Height, 1.0f, 100.0f);

            //m_projectionMatrix = FirstPersonShooterCamera.CreatePerspectiveFieldOfViewLH(MathHelper.ToRadians(45),
            //    (float)m_graphics.GraphicsDevice.Viewport.Width / (float)m_graphics.GraphicsDevice.Viewport.Height, 1.0f, 100.0f);

        }

        private void InitializeEffects()
        {
            CompiledEffect compiledEffect = Effect.CompileEffectFromFile(@"G:\agy\PostDoc\RenderVista\Projects\XNASubdivision\XNASubdivision\Content\Shaders\specular.fx"
                , null, null, CompilerOptions.None, TargetPlatform.Windows);

            if (!compiledEffect.Success)
            {
                MessageBox.Show(compiledEffect.ErrorsAndWarnings);
            }
            else
            {
                EffectPool globalPool = new EffectPool();
                m_effect = new Effect(m_graphics.GraphicsDevice, compiledEffect.GetEffectCode(), CompilerOptions.None, globalPool);
            }

            m_texture2D = Texture2D.FromFile(m_graphics.GraphicsDevice, @"G:\agy\PostDoc\RenderVista\Projects\XNASubdivision\XNASubdivision\Content\Textures\wp2_1024.jpg");

            m_effect.Parameters["g_txScene"].SetValue(m_texture2D);
            m_effect.Parameters["Opacity"].SetValue(1.0f);
            m_effect.Parameters["Diffuse"].SetValue(m_diffuseColor.ToVector4());


            m_basicEffectVertexDecl = new VertexDeclaration(m_graphics.GraphicsDevice, VertexPositionNormalTexture.VertexElements);


            m_basicEffect = new BasicEffect(m_graphics.GraphicsDevice, null);
            /*basicEffect.Alpha = 1.0f;
            basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 1.0f);
            basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
            basicEffect.SpecularPower = 5.0f;
            basicEffect.AmbientLightColor = new Vector3(0.75f, 0.75f, 0.75f);

            basicEffect.DirectionalLight0.Enabled = true;
            basicEffect.DirectionalLight0.DiffuseColor = Vector3.One;
            basicEffect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(1.0f, -1.0f, -1.0f));
            basicEffect.DirectionalLight0.SpecularColor = Vector3.One;

            basicEffect.DirectionalLight1.Enabled = true;
            basicEffect.DirectionalLight1.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);
            basicEffect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(-1.0f, -1.0f, 1.0f));
            basicEffect.DirectionalLight1.SpecularColor = new Vector3(0.5f, 0.5f, 0.5f);

            basicEffect.LightingEnabled = true;*/

            //basicEffect.Texture = texture;
            m_basicEffect.TextureEnabled = true;

            m_basicEffect.EnableDefaultLighting();

            //m_basicEffect.World = m_worldMatrix;
            //m_basicEffect.View = m_viewMatrix;
            //m_basicEffect.Projection = m_projectionMatrix;

            Console.WriteLine("init effect");
        }

        private void InitializeTriad()
        {
            m_vertexDeclTriad = new VertexDeclaration(m_graphics.GraphicsDevice, VertexPositionColor.VertexElements);

            VertexPositionColor[] pointList = new VertexPositionColor[6];
            pointList[0] = new VertexPositionColor(Vector3.Zero, Color.White);
            pointList[1] = new VertexPositionColor(new Vector3(1, 0, 0), Color.White);
            pointList[2] = new VertexPositionColor(Vector3.Zero, Color.Green);
            pointList[3] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Green);
            pointList[4] = new VertexPositionColor(Vector3.Zero, new Color(255, 255, 255, 255));
            pointList[5] = new VertexPositionColor(new Vector3(0, 0, 1), new Color(255, 0, 0, 0));

            // Initialize the vertex buffer, allocating memory for each vertex
            m_vertexBufferTriad = new VertexBuffer(m_graphics.GraphicsDevice,
                VertexPositionColor.SizeInBytes * pointList.Length,
                ResourceUsage.None, ResourceManagementMode.Automatic);

            // Set the vertex buffer data to the array of vertices
            m_vertexBufferTriad.SetData<VertexPositionColor>(pointList);
        }

      

        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                //texture = content.Load<Texture2D>("Content\\Textures\\asteroid1");
                //basicEffect.Texture = texture;

                //string filename = @"G:\agy\PostDoc\myPapers\subdiv 2007\Models\SimpleQuadRightHandledSystem\SimpleQuadRightHandledSystem.obj";
                //string filename = @"G:\agy\PostDoc\myPapers\subdiv 2007\Models\SimpleCubeRightHandledSystem\SimpleCubeRightHandledSystem.obj";
                //string filename = @"G:\agy\PostDoc\myPapers\subdiv 2007\Models\SimpleCubeRightHandledSystem2\SimpleCubeRightHandledSystem.obj";

                //string filename = @"G:\agy\PostDoc\myPapers\subdiv 2007\Models\Reptile\monsterfrog_mapped_00.obj";
                string filename = @"G:\agy\PostDoc\myPapers\subdiv 2007\Models\Reptile\monsterfrog_mapped_00_3.obj";
                m_quadMeshes.Add(new QuadMesh(filename));
                m_triangleMesh = new DXTriangleMesh(m_graphics.GraphicsDevice, m_quadMeshes[0]);

                m_font = m_contentManager.Load<SpriteFont>("SpriteFont1");
                m_foregroundBatch = new SpriteBatch(m_graphics.GraphicsDevice);

                //m_triangleMesh = DXTriangleMesh.CreateDemoRect(m_graphics.GraphicsDevice);
            }
        }

        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent == true)
            {
                m_contentManager.Unload();
            }
        }

        protected override void Update(GameTime gameTime)
        {

            KeyboardState newState = Keyboard.GetState();

            // Check to see if the Spacebar is down
            if (newState.IsKeyDown(Keys.Space))
            {
                //Key has just been pressed
                
            }
            if (newState.IsKeyDown(Keys.Escape))
            {
                Application.Exit();
            }
            // otherwise, check to see if was down before
            // (and therefore just released)
            else if (newState.IsKeyDown(Keys.Space) && !m_oldKeyboardState.IsKeyDown(Keys.Space))
            {
                m_isWireFrame = !m_isWireFrame;
                //MessageBox.Show("Space unpressed");
                //Key has just been released
            }

            if (newState.IsKeyDown(Keys.Tab) && !m_oldKeyboardState.IsKeyDown(Keys.Tab))
            {
                m_isDrawTriad = !m_isDrawTriad;
            }

            if (newState.IsKeyDown(Keys.D1) && !m_oldKeyboardState.IsKeyDown(Keys.D1))
            {
                ChangeToSubdivLevel(0);
            }

            if (newState.IsKeyDown(Keys.D2) && !m_oldKeyboardState.IsKeyDown(Keys.D2))
            {
                ChangeToSubdivLevel(1);
            }

            if (newState.IsKeyDown(Keys.D3) && !m_oldKeyboardState.IsKeyDown(Keys.D3))
            {
                ChangeToSubdivLevel(2);
            }

            if (newState.IsKeyDown(Keys.D4) && !m_oldKeyboardState.IsKeyDown(Keys.D4))
            {
                ChangeToSubdivLevel(3);
            }

            if (newState.IsKeyDown(Keys.D5) && !m_oldKeyboardState.IsKeyDown(Keys.D5))
            {
                ChangeToSubdivLevel(4);
            }

            if (newState.IsKeyDown(Keys.D6) && !m_oldKeyboardState.IsKeyDown(Keys.D6))
            {
                ChangeToSubdivLevel(5);
            }

            //ProcessInput((float)gameTime.ElapsedGameTime.Milliseconds / 30.0f);
            //UpdateCameraMatrices();


            m_oldKeyboardState = newState;
            base.Update(gameTime);
        }


        void ChangeToSubdivLevel(int p_level)
        {
            //if (p_level == 0)
            //{
            //    m_triangleMesh = new DXTriangleMesh(m_graphics.GraphicsDevice, m_quadMeshes[0]);
            //    return;
            //}

            m_triangleMesh = m_subdividerGSImitation.GetTriangleMesh(m_graphics.GraphicsDevice, m_quadMeshes[0], p_level);
        }


        void ChangeToSubdivLevelOld(int p_level)
        {
            if (p_level == 0)
            {
                m_triangleMesh = new DXTriangleMesh(m_graphics.GraphicsDevice, m_quadMeshes[0]);
                return;
            }

            int nInsertedItems = p_level - m_quadMeshes.Count + 1;
            for (int i = 0; i < nInsertedItems; i++)
			{
			    m_quadMeshes.Add(null);
			}

            for (int i = 0; i <= p_level; i++)
            {
                if (m_quadMeshes[i] == null)
                {
                    m_quadMeshes[i] = m_subdivider.GetSubdividedMesh(m_quadMeshes[i - 1]);
                }
            }

            m_triangleMesh = new DXTriangleMesh(m_graphics.GraphicsDevice, m_quadMeshes[p_level]);
        }


        protected override void Draw(GameTime gameTime)
        {
           // graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
           // basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 0.0f);


            //QuadMesh tempQuadMesh = m_subdivider.GetSubdividedMesh(m_quadMeshes[0]);

            //m_triangleMesh = new DXTriangleMesh(m_graphics.GraphicsDevice, tempQuadMesh);


            m_graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            
            m_basicEffect.World = m_worldMatrix;
            m_basicEffect.View = m_camera.View;
            m_basicEffect.Projection = m_camera.Projection;

            m_effect.Parameters["g_mWorld"].SetValue(m_worldMatrix);
            m_effect.Parameters["g_mView"].SetValue(m_camera.View);
            m_effect.Parameters["g_mProj"].SetValue(m_camera.Projection);
            Vector3 lightDir = Vector3.Normalize(m_camera.Forward);
            m_effect.Parameters["g_vLight"].SetValue(new float[] { lightDir.X, lightDir.Y, lightDir.Z, 1.0f });

           

            //m_graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace; // default, it was the default in DX9
            // stick to openGL right hand mode: front faces = CCW defined -> cull CW
            // !!stupid Xna: they changed to RH, but not changed the Culling default: http://daveremba.com/OBJ2DX_article/OBJ2DX_article.html
            
            m_graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;        // OpenGL RH mode, not the default DX cull
            //m_graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

 
            if (m_isWireFrame)
            {
                m_graphics.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
                m_basicEffect.LightingEnabled = false;
            }
            else
            {
                m_graphics.GraphicsDevice.RenderState.FillMode = FillMode.Solid;
                m_basicEffect.LightingEnabled = true;
            }

            if (m_triangleMesh != null)
            {
                m_graphics.GraphicsDevice.VertexDeclaration = m_triangleMesh.VertexDeclaration;
                m_graphics.GraphicsDevice.Vertices[0].SetSource(m_triangleMesh.VertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                m_graphics.GraphicsDevice.Indices = m_triangleMesh.IndexBuffer;

                m_effect.Begin();
                foreach (EffectPass pass in m_effect.CurrentTechnique.Passes)
                {
                    pass.Begin();

                    m_graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, m_triangleMesh.VertexCount, 0, m_triangleMesh.FaceCount);

                    pass.End();
                }
                m_effect.End();
            }

            //m_basicEffect.Begin();
            //foreach (EffectPass pass in m_basicEffect.CurrentTechnique.Passes)
            //{
            //    pass.Begin();

            //    m_graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, m_triangleMesh.VertexCount, 0, m_triangleMesh.FaceCount);

            //    pass.End();
            //}
            //m_basicEffect.End();

            //if (m_isDrawTriad)
            //    DrawTriadLines();


            UpdateFrameStats(gameTime);
            m_foregroundBatch.Begin();
            m_foregroundBatch.DrawString(m_font, m_statsData.currentFrameRate, new Vector2(10, 0), Color.LightGreen);
            m_foregroundBatch.End();

            base.Draw(gameTime);
        }

        public void UpdateFrameStats(GameTime p_gameTime)
        {
            if (!m_isShowFps)
                return;
            m_statsData.nRenderedFrames++;

            if (p_gameTime.TotalRealTime.TotalSeconds - m_statsData.lastStatsUpdateTimeSec > 1.0)   // update in each seconds
            {
                float fps = (float)(m_statsData.nRenderedFrames / (p_gameTime.TotalRealTime.TotalSeconds - m_statsData.lastStatsUpdateTimeSec));
                float msecondPerFrame = (float)(p_gameTime.TotalRealTime.TotalSeconds - m_statsData.lastStatsUpdateTimeSec) / m_statsData.nRenderedFrames;
                m_statsData.nRenderedFrames = 0;
                m_statsData.lastStatsUpdateTimeSec = p_gameTime.TotalRealTime.TotalSeconds;
                m_statsData.currentFrameRate = String.Format("fps = {0:f3} ({1:f3} msec) minfps = {2:f3} ({3:f3} msec)",
                    fps, msecondPerFrame * 1000, m_statsData.minFPS, m_statsData.maxWait);
            }
        }

        private void DrawTriadLines()
        {
            // 2. draw triad lines, http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=816825&SiteID=1
            m_graphics.GraphicsDevice.RenderState.DepthBufferEnable = false;
            m_graphics.GraphicsDevice.VertexDeclaration = m_vertexDeclTriad;
            m_graphics.GraphicsDevice.Vertices[0].SetSource(m_vertexBufferTriad, 0, VertexPositionColor.SizeInBytes);
            m_basicEffect.LightingEnabled = false;
            m_basicEffect.Begin();
            foreach (EffectPass pass in m_basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                // X - Red
                m_basicEffect.DiffuseColor = new Vector3(1.0f, 0.0f, 0.0f);
                m_basicEffect.CommitChanges();
                m_graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, 1);

                // Y - Green
                m_basicEffect.DiffuseColor = new Vector3(0.0f, 1.0f, 0.0f);
                m_basicEffect.CommitChanges();
                m_graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 2, 1);

                // Z - Blue
                m_basicEffect.DiffuseColor = new Vector3(0.0f, 0.0f, 1.0f);
                m_basicEffect.CommitChanges();
                m_graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 4, 1);
                pass.End();
            }
            m_basicEffect.End();

            m_graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_basicEffect.LightingEnabled = true;
        }
    }
}
